#include <iostream>
#include <memory>
#include <vector>
#include <algorithm>
#include <functional>
#include <fstream>
#include <map>

#include <sys/select.h>
#include <unistd.h>
#include <signal.h>
#include <math.h>

#include "commons.hpp"
#include "server.hpp"
#include "socket.hpp"
#include "player.hpp"
#include "protocol.hpp"
#include "object.hpp"
#include "rle.hpp"

#include "medved/manager.hpp"
#include "medved/shared_ptr.hpp"

using std::cout;
using std::cerr;
using namespace Net;
using namespace Proto;
using namespace Server;
using namespace Medved;
namespace C2S = Client2Server;

bool isAlive(const PlayerData &pd)
{
	return pd.isAlive;
}

inline bool isVisibleThrough(ObjectData field[FIELD_COLS][FIELD_ROWS], short x, short y)
{
	return field[x][y].objectId() == FIELD_EMPTY && !field[x][y].getPlayer();
}

bool isVisible(ObjectData field[FIELD_COLS][FIELD_ROWS], const Player &player, short tx, short ty)
{
	if (vectorModulo(tx - player.x, ty - player.y) > player.visualSector)
		return false;

	// using Bresenham's algorighm
	short x0 = player.x;
	short y0 = player.y;
	short x1 = tx;
	short y1 = ty;
	short tryCount = !isVisibleThrough(field, tx, ty) + 2;
	bool swapped = abs(y1 - y0) > abs(x1 - x0);
	
	if (swapped) {
		std::swap(x0, y0);
		std::swap(x1, y1);
	}
	if (x0 > x1) {
		std::swap(x0, x1);
		std::swap(y0, y1);
	}
	short dx = x1 - x0;
	short dy = abs(y1 - y0);
	short error = -dx/2;
	short ystep = y0 < y1 ? 1 : -1;
	short y = y0;
	for (short x = x0; x <= x1; ++x) {
		if (swapped) {
			if (!isVisibleThrough(field, y, x) && !(--tryCount))
				return false;
		}
		else {
			if (!isVisibleThrough(field, x, y) && !(--tryCount))
				return false;
		}
		error += dy;
		if (error > 0) {
			y += ystep;
			error -= dx;
		}
	}
	
	return true;
}

inline void dropDeadPlayers(std::vector<PlayerData> &players)
{
	bool isAnyErased = false;
	for (int i = players.size() - 1; i >= 0; --i)
		if (!players[i].isAlive) {
			try {
				players[i].socket->send(static_cast<int>(players[i].status));
			}
			catch (...)	{
				// just suppress exceptions
			}
			players.erase(players.begin() + i);
			isAnyErased = true;
		}
	if (isAnyErased)
		cout << players.size() << " players left\n";
}

std::ostream &operator <<(std::ostream &out, const PlayerData &player)
{
	return out << player.nick <<  " from [" << player.socket->addressString() << ":" << player.socket->port() << "]";
}

void disconnect(PlayerData &player, const char *reason, int status)
{
	cout << "Player " << player << " disconnected due to " << reason << std::endl;
	player.status = status;
	player.kill();
}

void ObjectData::enter(PlayerData &pd)
{
	playerId_ = pd.getId();
	ObjectHolder::enter(pd);
}

void ObjectData::leave(PlayerData &pd)
{
	playerId_ = -1;
	ObjectHolder::leave(pd);
}

PlayerData *ObjectData::getPlayer()
{
	if (playerId_ == -1)
		return 0;
	PlayerData *player =
			static_cast<PlayerData *>(TheManager::instance().get(playerId_));
	if (!player || !player->isAlive) {
		playerId_ = -1;
		return 0;
	}
	return player;
}

struct TurnAction
{
	TurnAction(std::vector<PlayerData> &players__,	ObjectData (&field__)[FIELD_COLS][FIELD_ROWS]) :
		players(players__), field(field__)
	{}
	
protected:
	std::vector<PlayerData> &players;
	ObjectData (&field)[FIELD_COLS][FIELD_ROWS];
};

#define IMPLEMENT_TURN_ACTION_CONSTRUCTOR(name) \
	name(std::vector<PlayerData> &players__,	ObjectData (&field__)[FIELD_COLS][FIELD_ROWS]) : \
		TurnAction(players__, field__)\
		{}

struct SendData : public TurnAction
{
	IMPLEMENT_TURN_ACTION_CONSTRUCTOR(SendData)

	void operator()(PlayerData &p)
	{
		static RLEncoder encoder;

		encoder.reset();
		for (int y = 0; y < FIELD_ROWS; ++y)
			for (int x = 0; x < FIELD_COLS; ++x) {			
				unsigned char val;
				if (!isVisible(field, p, x, y))
					val = FIELD_NOT_VISIBLE;
				else if (field[x][y].getPlayer() && field[x][y].getPlayer() != &p)
					val = FIELD_ENEMY;
				else
					val = field[x][y].objectId();
			encoder.charge(val);
			}
		encoder.commitEncode();
		try {
			p.socket->send(static_cast<int>(RESULT_NONE));
			// player data
			// just send entire Player structure
			p.socket->send(static_cast<const Player&>(p));
			// field
			p.socket->send(encoder.getLength());
			p.socket->send(encoder.getBuffer(), encoder.getLength());
		}
		catch (const TCPSocket::Exception &e) {
			disconnect(p, "problem with network communication", 0);
		}
	}
};

struct RecvData : public TurnAction
{
	IMPLEMENT_TURN_ACTION_CONSTRUCTOR(RecvData)
	
	void operator()(PlayerData &p)
	{
		try {
			p.socket->recv(p.speedAdjust);
			p.socket->recv(p.fire);
		}
		catch (const TCPSocket::Exception &e) {
			disconnect(p, "problem with network communication", 0);
		}
	}
};

struct AdjustVelocity : public TurnAction
{
	IMPLEMENT_TURN_ACTION_CONSTRUCTOR(AdjustVelocity)
	
	void operator()(PlayerData &p)
	{
		if (p.speedAdjust.isBraking) {
			p.brake();
		}
		else {
			if (abs(p.speedAdjust.xAdjust) > 1 || abs(p.speedAdjust.yAdjust > 1)) {
				disconnect(p, "cheating with speed adjusting command", RESULT_CHEATING);
				return;
			}
			if (!p.adjustVelocity(p.speedAdjust.xAdjust, p.speedAdjust.yAdjust)) {
				disconnect(p, "cheating with maximum velocity", RESULT_CHEATING);
				return;
			}
		}
	}
};

struct Move : public TurnAction
{
	IMPLEMENT_TURN_ACTION_CONSTRUCTOR(Move)
	
	void operator()(PlayerData &p)
	{
		int factor = 1;
		if (field[p.x][p.y].objectId() == FIELD_SWAMP)
			factor = SWAMP_VISCOSITY_FACTOR;
		if (p.energy < unitsToEnergy(p.velocityModulo())*factor || p.velocityModulo() > p.maxVelocity) {
			// full stop
			p.brake();
		}
		p.energy -= unitsToEnergy(p.velocityModulo())*factor;

		short old_x = p.x, old_y = p.y;
		short old_xacc = p.xAccum, old_yacc = p.yAccum;

		p.xAccum += unitsToVelocity(p.velx);
		p.yAccum += unitsToVelocity(p.vely);

		// awful copy-paste here :(
		if (p.xAccum < 0) {
			p.xAccum += Player::COORDINATE_SCALE;
			--p.x;
		}
		if (p.xAccum >= Player::COORDINATE_SCALE) {
			p.xAccum -= Player::COORDINATE_SCALE;
			++p.x;
		}
		if (p.yAccum < 0) {
			p.yAccum += Player::COORDINATE_SCALE;
			--p.y;
		}
		if (p.yAccum >= Player::COORDINATE_SCALE) {
			p.yAccum -= Player::COORDINATE_SCALE;
			++p.y;
		}

		if (p.x >= FIELD_COLS) {
			p.x = old_x;
			p.xAccum = old_xacc;
			p.velx = 0;
		}
		else if (p.x < 0) {
			p.x = old_x;
			p.xAccum = old_xacc;
			p.velx = 0;
		}

		if (p.y >= FIELD_ROWS) {
			p.y = old_y;
			p.yAccum = old_yacc;
			p.vely = 0;
		}
		else if (p.y < 0) {
			p.y = old_y;
			p.yAccum = old_yacc;
			p.vely = 0;
		}

		// apply bonuses/obstacles/charge station effects
		if (old_x != p.x || old_y != p.y) {
			// check if someone already here
			if (field[p.x][p.y].getPlayer() ||
				!field[p.x][p.y].isEnterable()) {
				p.x = old_x;
				p.y = old_y;
				p.xAccum = old_xacc;
				p.yAccum = old_yacc;
				p.brake();
			}
			if (field[p.x][p.y].objectId() == FIELD_BONUS) {
				cout << "Player " << p << " picked up a bonus\n";
			}

			field[old_x][old_y].leave(p);
			field[old_x][old_y].setPlayerId(-1);
			field[p.x][p.y].enter(p);
			if (!p.isAlive) {
				cout << "Player " << p << " is killed by an obstacle\n";
				p.status = RESULT_KILLED;
			}
			else
				field[p.x][p.y].setPlayerId(p.getId());
		}
		else {
				field[old_x][old_y].timeStep(p);
		}
	}
};

struct Shoot : public TurnAction
{
	IMPLEMENT_TURN_ACTION_CONSTRUCTOR(Shoot)
	
	void operator()(PlayerData &p)
	{
		if (p.shootCooldown)
			--p.shootCooldown;
		if (!p.fire.power)
			return;
		if (p.shootCooldown) {
			disconnect(p, "shooting too often", RESULT_CHEATING);
			return;
		}
		if (p.fire.xCoord < 0 || p.fire.xCoord >= FIELD_COLS ||
				p.fire.yCoord < 0 || p.fire.yCoord >= FIELD_ROWS) {
			disconnect(p, "shooting out of the screen", RESULT_CHEATING);
			return;
		}
		
		if (!p.isCouldShoot(p.fire.xCoord, p.fire.yCoord)) {
			disconnect(p, "shooting out of shooting sector", RESULT_CHEATING);
			return;
		}
		
		if (p.fire.power < 1 || p.fire.power > p.maxFire) {
			disconnect(p, "shoot power out of range", RESULT_CHEATING);
			return;
		}
		if (p.fire.power > p.energy)
			p.fire.power = p.energy;
		p.energy -= p.fire.power;
		// remember, player on a charge station are undestructable
		if (field[p.fire.xCoord][p.fire.yCoord].getPlayer() &&
			field[p.fire.xCoord][p.fire.yCoord].objectId() != FIELD_CHARGE_STATION) {
			PlayerData *player = field[p.fire.xCoord][p.fire.yCoord].getPlayer();
			if (player) { 
				player->adjustDurability(-p.fire.power);
				if (!player->isAlive) {
					cout << "Player " << *player << " was killed by player " << p << '\n';
					player->status = RESULT_KILLED;
				}
			}
		}
		field[p.fire.xCoord][p.fire.yCoord].damage(p.fire.power);
		p.shootCooldown = PLAYER_SHOOT_COOLDOWN;
	}
};

bool acceptConnections(TCPSocket &serverSocket, std::vector<PlayerData> &players)
{
	// awful mix of c/c++ styles
	// i hate it, but what should i do instead?
	//
	try {
		for (;;) {
			cout << "Ready to accept\n";
			fd_set set;

			FD_ZERO(&set);
			FD_SET(0, &set);
			FD_SET(serverSocket.getImpl(), &set);

	
			if (select(FD_SETSIZE, &set, 0, 0, 0) <= 0) {
				cerr << "select failed\n";
				return 1;
			}
			if (FD_ISSET(0, &set))
				break;
			else {
				players.push_back(serverSocket.accept());
				players.back().socket->recv(players.back().nick, NICK_LENGTH);
				players.back().nick[NICK_LENGTH] = 0;
				cout << "A player connected:\n";
				cout << '\t' << players.back() << '\n';
				if (players.size() == MAX_PLAYERS) {
					cout << "Maximum number of players have connected. Starting automatically\n";
					break;
				}
			}
		}
	}
	catch (const TCPSocket::Exception &e)
	{
		cerr << "Error while accepting connections\n";
		return 1;
	}

	// drop all inputed data
	for (;;) {
		fd_set set;
		struct timeval tv;

		FD_ZERO(&set);
		FD_SET(0, &set);
		tv.tv_sec = 0;
		tv.tv_usec = 0;
		int result;
		if ((result = select(FD_SETSIZE, &set, 0, 0, &tv)) < 0) {
			cerr << "select failed\n";
			return 1;
		}
		if (!result)
			break;
		getc(stdin);
	}
	return !players.empty();
}

bool readField(ObjectData field[FIELD_COLS][FIELD_ROWS])
{
	// read field
	std::ifstream in("field");
	if (!in) {
		cerr << "Cannot read field from file\n";
		cerr << "Using no field\n";
		return false;
	}
	else {
		std::map<char, SharedPtr<Object> >factory;
		factory['r'] = new River;
		factory['b'] = new Bridge;
		factory['s'] = new Swamp;
		factory['f'] = new Forest;
		factory['+'] = new ChargeStation;
		
		try {
			char c;
			for (int y = 0; y < FIELD_ROWS; ++y)
				for (int x = 0; x < FIELD_COLS; ++x) {
					if (!(in >> c))
						throw c;
					if (c == '.')
						continue;
					if (!factory[tolower(c)])
						throw c;
					field[x][y].setObject(factory[tolower(c)]->clone());
				}
		}
		catch (...) {
			cerr << "Unsupported field format\n";
			return false;
		}
		return true;
	}
}

void removeBonus(ObjectData &od, int objectNumber)
{
	if (od.getObjectNumber() == objectNumber)
		od.setObject(0);
}

void generateBonus(ObjectData &od, const std::vector<SharedPtr<Bonus> > &bonusFactory)
{
	od.setObject(bonusFactory[rand()%bonusFactory.size()]->clone());
	TheScheduler::instance().schedule(BONUS_LIFETIME, bind(
			removeBonus, od, od.getObjectNumber()));
}

volatile bool stoppedFlag = false;

void sigHnd(int)
{
	stoppedFlag = true;
}

int main(void)
{
	TCPSocket serverSocket(SERVER_PORT);

	std::vector<PlayerData> players;
	ObjectData field[FIELD_COLS][FIELD_ROWS];

	// prevent `broken pipe'
	signal(SIGPIPE, SIG_IGN);

	signal(SIGINT, &sigHnd);

	cout << "Welcome to \"cursed robots\" super server at port "
			<< SERVER_PORT << "\n";
	cout << "Awaiting client connections\n";
	cout << "Press ENTER to begin game\n";

	serverSocket.listen();

	// yes, we really need this
	std::ios_base::sync_with_stdio(true);

	if (!readField(field)) {
		return 1;
	}

	// bonuses factory
	std::vector<SharedPtr<Bonus> > bonusFactory;
	bonusFactory.push_back(new VisualSectorBonus);
	bonusFactory.push_back(new ShootSectorBonus);
	bonusFactory.push_back(new MaxEnergyBonus);
	bonusFactory.push_back(new MaxFireBonus);
	bonusFactory.push_back(new MaxVelocityBonus);
	
	
	if (!acceptConnections(serverSocket, players)) {
		cout << "No players were connected\n";
		cout << "Halt\n";
		return 0;
	}
	cout << players.size() << " players were connected\n";

	
	srand(time(0));
	
	// setup starting positions for players
	for (std::vector<PlayerData>::iterator i = players.begin(); i != players.end(); ++i) {
		do {
			i->x = rand()%FIELD_COLS;
			i->y = rand()%FIELD_ROWS;
		} while (field[i->x][i->y].getPlayer() ||
				field[i->x][i->y].objectId() != FIELD_EMPTY);
		i->xAccum = Player::COORDINATE_SCALE/2;
		i->yAccum = Player::COORDINATE_SCALE/2;
		field[i->x][i->y].setPlayerId(i->getId());
	}
	
	// create objects for turn actions
	SendData sendData(players, field);
	RecvData recvData(players, field);
	AdjustVelocity adjustVelocity(players, field);
	Shoot shoot(players, field);
	Move move(players, field);

	// main game cycle
	while (players.size() > 1 && !stoppedFlag) {
		std::for_each(players.begin(), players.end(), sendData);
		dropDeadPlayers(players);
		// do not break there because there is maybe only one player,
		// if it will be not able to write data to socket, it will self-terminate
		// and therefore not report abount winnnig

		std::for_each(players.begin(), players.end(), recvData);
		dropDeadPlayers(players);
		if (players.size() <= 1)
			break;

		std::for_each(players.begin(), players.end(), shoot);
		dropDeadPlayers(players);
		if (players.size() <= 1)
			break;

		std::for_each(players.begin(), players.end(), adjustVelocity);
		dropDeadPlayers(players);
		if (players.size() <= 1)
			break;

		std::for_each(players.begin(), players.end(), move);
		dropDeadPlayers(players);
		if (players.size() <= 1)
			break;
		
		// generate bonuses
		if (rand()%BONUS_PROBABILITY == 0) {
			int x, y;
			int count = 128; // silly magic number :(
			do {
				x = rand()%FIELD_COLS;
				y = rand()%FIELD_ROWS;
				--count;
			} while ((field[x][y].objectId() != FIELD_EMPTY || field[x][y].getPlayer())
						&& count > 0);
			if (count) {
				generateBonus(field[x][y], bonusFactory);
			}
		}
		// !!!
		//0.1sec
		usleep(1000 * 1000 / 10);
		
		TheScheduler::instance().timeStep();
	}
	
	if (stoppedFlag) {
		// send messages to clients
		for (unsigned i = 0; i < players.size(); ++i) {
			players[i].status = RESULT_GAME_OVER;
			players[i].kill();
		}
		dropDeadPlayers(players);
	}
	
	if (players.size() == 1) {
		// we got the winner
		try {
			players[0].socket->send(static_cast<int>(RESULT_WINNER));
		}
		catch (...)	{}
		cout << "Player " << players[0] << " won\n";
	}

	cout << "Gamer over\n";
	return 0;
}


